home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / oldusers < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  63.7 KB  |  1,790 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) oldusers.gawk 4.0 97/02/07
  4. # 92/08/10 John H. DuBois III (john@armory.com)
  5. # 92/11/07 rewrote; fixed to work with a large number of users
  6. # 92/11/26 Do not report users who have not logged in for 6 mo. if preserved
  7. # 93/03/18 Use gawk to get strftime().  Made k[] global to work around gawk bug.
  8. #          Added <days> option.  Other general improvements.
  9. # 93/07/18 Rewrite with new, sensible options etc.
  10. # 93/12/13 Allow comments in preserved users file & check for nonexistant
  11. #          users in it.
  12. # 94/05/12 Added I option
  13. # 94/08/28 Added checking of login shells.
  14. # 95/04/15 Avoid timezone issues by working with days instead of seconds.
  15. # 95/09/12 Added -rtp options.  Let a list of users be given on command line.
  16. # 96/01/03 Ported to 5.0: deal with different 'l' message for file not found.
  17. #          Fixed s/b options mixup.
  18. # 96/11/14 Read rcfile.  Added N and L options.
  19. #          If no users of a type, report the oldest of that type.
  20. #          Get account creation time from shell rcfiles instead of home dir,
  21. # 97/02/07 Added M option.  Check for pop file as well as .lastlogin.
  22.  
  23. BEGIN {
  24.     Name = "oldusers"
  25.     Usage = \
  26. "Usage: " Name " [-bhnlrtsxp] [-u<minuid>] [-i<ignorefile>] [-dNL<days>]\n"\
  27. "        [-M<maxexecargs> [user ...]"
  28.     Days = 365
  29.     MinUID = 100
  30.     DefIgnFile = "/usr/local/lib/preserve_users"
  31.     rcFile = "/etc/default/" Name
  32.     ARGC = Opts(Name,Usage,"nlbd<u<i:spN<L<M>hx>rt",0,rcFile,
  33.     "NEVERONLY,LONGONLY,BRIEF,DAYS,MINUID,IGNOREFILE,NOSHELLTEST,PROGRESS,"\
  34.     "NEVERDAYS,LONGDAYS,MAXEXECARGS")
  35.     if ("h" in Options) {
  36.     printf \
  37. "%s: find users who have never logged in,\n"\
  38. "and find users who have not logged in for a specified period.\n"\
  39. "%s\n"\
  40. "Users who have not logged in for a given number of days and/or users who\n"\
  41. "have never logged in and whose accounts were created the given number of\n"\
  42. "days or more ago are listed.  The default is %d days.  If usernames are\n"\
  43. "given, only those users are checked, user are checked regardless of uid\n"\
  44. "unless -u is given, and the meaning of the -s and the -I options is\n"\
  45. "inverted: they are turned on by default; giving them turns them off.\n"\
  46. "If user names are not given on the command line, all users are checked.\n"\
  47. "In order to determine who is a real user and who is a pseudo-user (and\n"\
  48. "should therefore be ignored), the file /etc/shells is read.  If it exists,\n"\
  49. "it is taken to be a list of all of the real-user login shells.  Any user\n"\
  50. "whose login shell does not appear in it is ignored.  Also, regardless of\n"\
  51. "whether /etc/shells exists, any user who has no login shell specified is\n"\
  52. "ignored.  In addition, any user whose uid is less than a specified uid\n"\
  53. "(see the -u option) is ignored.\n"\
  54. "The last login time is taken from the modification time of the user's\n"\
  55. ".lastlogin and /usr/spool/mail/.<username>.pop file, whichever is more\n"\
  56. "recent.  The account creation time is taken from the modification time of\n"\
  57. "the user's shell startup files.\n"\
  58. "Options:\n"\
  59. "Some of the following options can also be set by assigning values to\n"\
  60. "variables in a configuration file named %s.  Variables are\n"\
  61. "assigned to with the syntax:  varname=value  or in the case of flags, by\n"\
  62. "simply putting the indicated variable name in the file without a value.\n"\
  63. "Options given on the command line override assigments in the configuration\n"\
  64. "file.  Flag options set in the configuration file can be turned off on the\n"\
  65. "command line by following them immediately with \"-\", e.g. -v- to turn\n"\
  66. "off the v option in such a way that it cannot be turned on in the config\n"\
  67. "file.  Variable names appear in parentheses in the option descriptions.\n"\
  68. "-h: print this help.\n"\
  69. "-n: Print never-logged-in users only.  (NEVERONLY)\n"\
  70. "-l: Print long-time-since-login users only.  (LONGONLY)\n"\
  71. "-b: Brief form: print login names only, without headers.  (BRIEF)\n"\
  72. "-x<level>: Turn on debugging at level <level>, which should be a positive\n"\
  73. "    integer.  Higher values cause more verbose diagnostics to be printed.\n"\
  74. "-M<maxexecargs>: Set the maximum number of characters on a command line to\n"\
  75. "    <maxexecargs>.  The default is 1000.  If the operating system supports\n"\
  76. "    a longer command line, setting it here will speed things up.\n"\
  77. "-d<days>: Set the # of days at which a user becomes an old user.  (DAYS)\n"\
  78. "-N<days>, -L<days>: Like -d, but apply to never-logged-in users and\n"\
  79. "    long-time-since-login users only respectively, and override -d for\n"\
  80. "    these purposes.  (NEVERDAYS, LONGDAYS)\n"\
  81. "-u<minuid>: Users with uids less than <minuid> are ignored (default: %d).\n"\
  82. "    (MINUID)\n"\
  83. "-i<ignorefile>: Users listed in <ignorefile> are ignored.  The default is\n"\
  84. "    %s.  Use -i \"\" or IGNOREFILE= to not have any\n"\
  85. "    ignorefile read.  (IGNOREFILE)\n"\
  86. "-r: Read a list of users to check from the standard input, one per line.\n"\
  87. "    Each line may include \"tag\" information, separated from the username\n"\
  88. "    by whitespace.  The tag is printed after the username.  The -sI\n"\
  89. "    options are treated as when usernames are given on the command line.\n"\
  90. "-t: Sort by the tag rather than the time.  This turns on the r option.\n"\
  91. "-s: Do not ignore users who have no login shell or whose login shell does\n"\
  92. "    not appear in /etc/shells.  (NOSHELLTEST)\n"\
  93. "-p: Print a progress report.  (PROGRESS)\n",
  94.     Name,Usage,Days,rcFile,MinUID,DefIgnFile
  95.  
  96.     exit 0
  97.     }
  98.     Brief = Options["b"]        # Short form (names only)
  99.     LoggedInOnly = "l" in Options    # Don't report on never-logged-in users
  100.     NeverLoggedInOnly = "n" in Options    # Report only on never-logged-in users
  101.     if ("M" in Options)
  102.     MAXEXECARGS = Options["M"]
  103.     ChkShell = !("s"in Options)
  104.     if ("x" in Options) {
  105.     Debug = Options["x"]
  106.     printf "Debugging enabled at level %d\n",Debug > "/dev/stderr"
  107.     }
  108.     Verbose = "p" in Options || Debug
  109.     TagSort = "t" in Options    # sort by tag value rather than tiem
  110.     ReadInput = "r" in Options || TagSort
  111.     if ("d" in Options)
  112.     Days = Options["d"]
  113.     NeverDays = "N" in Options ? Options["N"] : Days
  114.     LongDays = "L" in Options ? Options["L"] : Days
  115.     if ("i" in Options)
  116.     DefIgnFile = Options["i"]
  117.     ReadIgnore = (DefIgnFile != "")
  118.     if ("u" in Options)
  119.     MinUID = Options["u"]
  120.  
  121.     if (ARGC > 1) {
  122.     UsersGiven = 1
  123.     for (i = 1; i < ARGC; i++)
  124.         UserTags[ARGV[i]]
  125.     }
  126.     if (ReadInput) {
  127.     if (Verbose)
  128.         print "Reading specified-users list..." > "/dev/stderr"
  129.     UsersGiven = 1
  130.     while (getline)
  131.         if (NF) {
  132.         User = $1
  133.         if (NF > 1) {
  134.             Tag = $0
  135.             sub(/^[^ \t]+[ \t]+/,"",Tag)
  136.         }
  137.         else
  138.             Tag = ""
  139.         UserTags[User] = Tag
  140.         }
  141.     }
  142.     if (UsersGiven) {
  143.     ReadIgnore = !ReadIgnore
  144.     ChkShell = !ChkShell
  145.     MinUID = -1
  146.     }
  147.     # Make the indexes of PreserveUsers[] be the set preserved users
  148.     if (ReadIgnore) {
  149.     if (Verbose)
  150.         print "Reading ignored-users list..." > "/dev/stderr"
  151.     while ((err = (getline user < DefIgnFile)) == 1)
  152.         if (user !~ /^#/) {
  153.         gsub("[ \t].*","",user)    # get rid of whitespace/comments
  154.         PreserveUsers[user]
  155.         }
  156.     }
  157.     if (err == -1 && DefIgnFile != "") {
  158.     printf "Error reading ignored users file \"%s\"\n",
  159.     DefIgnFile > "/dev/stderr"
  160.     exit(1)
  161.     }
  162.     # Initialize month name/number lookup tables
  163.     MkMonth2Num()
  164.  
  165.     Num = 0
  166.  
  167.     # Ignore users without real-user login shells.
  168.     if (ChkShell) {
  169.     if (Verbose)
  170.         print "Reading login shells list..." > "/dev/stderr"
  171.     ReadShells(Shells)
  172.     }
  173.     for (User in UserTags)
  174.     Users[User]
  175.     CurDay = int(systime()/(24*3600))
  176.     ChkPaths["~/.lastlogin"]
  177.     ChkPaths["/usr/spool/mail/.%u.pop"]
  178.     GetTimes(MinUID,PreserveUsers,ChkShell,Shells,UsersGiven,Users,Verbose,
  179.     NeverDays,LongDays,LoggedInOnly,LLUsers,NLUsers,CurDay,ChkPaths)
  180.  
  181.     for (User in PreserveUsers)
  182.     printf "Preserved user not found in passwd database: %s\n",
  183.     User > "/dev/stderr"
  184.     for (User in Users)
  185.     printf "Specified user not found in passwd database: %s\n",
  186.     User > "/dev/stderr"
  187.  
  188.     if (!LoggedInOnly) {
  189.     Report(UserTags,NLUsers,
  190.     "Users created " NeverDays " or more days ago and never logged in:",
  191.     TagSort,Brief,CurDay)
  192.     }
  193.     if (!NeverLoggedInOnly) {
  194.     if (!LoggedInOnly && !Brief)
  195.         print ""
  196.     Report(UserTags,LLUsers,
  197.     "Users who have not logged in for " LongDays " days or more:",
  198.     TagSort,Brief,CurDay)
  199.     }
  200. }
  201.  
  202. # GetTimes: find last login times for users.
  203. # MinUID: minimum UID of users to check (0 if users should be checked
  204. #         regardless of UID).
  205. # PreserveUsers[]: set of account names to ignore.
  206. # ChkShell: true if only users with a shell in Shells[] shell should be checked
  207. # Shells[]: set of shells that real users have, if ChkShell is true.
  208. # UsersGiven: true if only users in Users should be checked.
  209. # Users[]: set of user names to check, if UsersGiven is true.
  210. # Verbose: true if actions should be printed.
  211. # NeverDays: Age that home dir may reach before never logged into account is
  212. # considered old.
  213. # LongDays: Age that a login timestamp file may reach before account is
  214. # considered old.
  215. # LoggedInOnly: True if only need check on users who logged in at least once.
  216. # Returns values:
  217. # All users found are deleted from Users[] and PreserveUsers[].
  218. # The following return times as UNIX epoch day, indexed by user:
  219. # LLUsers[]: For users who have logged in, last login time.
  220. # NLUsers[]: For users who have never logged in, account creation time.
  221. # CurDay: Current day, in UNIX epoch format.
  222. # ChkPaths: Set of paths to check for each user.  For each path, the ~ in a
  223. # leading ~/ is replaced by the user's home directory, and the first instance
  224. # of %u is replaced by the user's account name.
  225. # The index OLDEST is also set in each array, if there is any user of the given
  226. # type; its value is the timestamp and username of the oldest account of the
  227. # given type (even if that user is not put in the array due to the thresholds),
  228. # separated by a semicolon.
  229. function GetTimes(MinUID,PreserveUsers,ChkShell,Shells,UsersGiven,Users,
  230. Verbose,NeverDays,LongDays,LoggedInOnly,LLUsers,NLUsers,CurDay,ChkPaths,
  231. PWEnt,User,LLFiles,oldestLogin,oldestCreated,path,File2User,Date,
  232. MinDate,UserFiles) {
  233.     if (Debug)
  234.     printf "GetTime(): NeverDays = %d; LongDays = %d\n",NeverDays,
  235.     LongDays > "/dev/stderr"
  236.     split("",PWEnt)    # let gawk know this is an array
  237.     if (Verbose)
  238.     print "Reading password file..." > "/dev/stderr"
  239.     while (getpwent(PWEnt)) {
  240.     # Ignore system users
  241.     User = PWEnt[PW_NAME]
  242.     if (!(PWEnt[PW_UID] < MinUID || User in PreserveUsers ||
  243.     ChkShell && !(PWEnt[PW_SHELL] in Shells)) &&
  244.     (!UsersGiven || User in Users))
  245.         LLUsers[User]
  246.     delete PreserveUsers[User]
  247.     delete Users[User]
  248.     }
  249.  
  250.     if (Verbose)
  251.     print "Checking last login times..." > "/dev/stderr"
  252.     # Find users who have not logged in in a long time,
  253.     # and who do not have login timestamp files at all.
  254.     GetUserTimestamps(LLUsers,ChkPaths,1,UserFiles)
  255.  
  256.     MinDate = CurDay - LongDays
  257.     for (User in LLUsers) {
  258.     Date = LLUsers[User]
  259.     if (Date == "") {    # Never logged in
  260.         NLUsers[User]
  261.         delete LLUsers[User]
  262.         continue
  263.     }
  264.     if (oldestLogin == "" || Date < oldestLogin) {
  265.         oldestLogin = LLUsers[User]
  266.         oldestUser = User
  267.     }
  268.     if (Debug > 5)
  269.         printf "timestamp for %8s: %d; file used: %s\n",
  270.         User,LLUsers[User],UserFiles[User] > "/dev/stderr"
  271.     if (Date > MinDate)    # Has logged in in the given time period
  272.         delete LLUsers[User]
  273.     }
  274.     if (oldestLogin != "")
  275.     LLUsers["OLDEST"] = oldestLogin ";" oldestUser
  276.     if (!LoggedInOnly) {
  277.     if (Verbose)
  278.         print "Checking account creation times..." > "/dev/stderr"
  279.     oldestCreated = FindWhenCreated(NeverDays,NLUsers,CurDay - NeverDays)
  280.     if (oldestCreated != "")
  281.         NLUsers["OLDEST"] = oldestCreated
  282.     }
  283. }
  284.  
  285. # For each user in Users[], each of the paths in ChkPaths[] is convert to a
  286. # path for a user-specific file.  For each user, the newest or oldest file is
  287. # found (depending on whether Newest is true).  The modification time of the
  288. # file is returned as the value for that user's index in Users[], as a UNIX
  289. # epoch day.  If none of the files exist for a user, the value of that user's
  290. # index is not modified.
  291. # ChkPaths: Set of paths to check for each user.  For each path, the ~ in a
  292. # leading ~/ is replaced by the user's home directory, and the first instance
  293. # of %u is replaced by the user's account name.
  294. # The name of the selected file for each user is returned in UserFiles[],
  295. # index by user name.
  296. function GetUserTimestamps(Users,ChkPaths,Newest,UserFiles,
  297. LLFiles,path,User,PWEnt,File2User,NotFound,File,Date) {
  298.     for (User in Users)
  299.     if (getpwnam(User,PWEnt))
  300.         for (path in ChkPaths) {
  301.         sub("^~/",PWEnt[PW_HOME] "/",path)
  302.         sub("%u",User,path)
  303.         LLFiles[path]
  304.         File2User[path] = User
  305.         }
  306.     else
  307.         printf "GetUserTimestamps(): No such user: %s\n",
  308.         User > "/dev/stderr"
  309.     GetFileDates(LLFiles,NotFound)
  310.     for (File in LLFiles) {
  311.     if (!(File in File2User)) {
  312.         printf "GetUserTimestamps(): file %s not in File2User?!\n",
  313.         File > "/dev/stderr"
  314.         continue
  315.     }
  316.     if (File in NotFound)
  317.         continue
  318.     User = File2User[File]
  319.     if (!(User in Users)) {
  320.         printf "GetUserTimestamps(): User %s not in Users?!\n",
  321.         User,File > "/dev/stderr"
  322.         continue
  323.     }
  324.     Date = LLFiles[File]
  325.     if (Date == "") {
  326.         printf "GetUserTimestamps(): No output from 'l' for file %s.\n",
  327.         File > "/dev/stderr"
  328.         continue
  329.     }
  330.     if (Users[User] == "" || Newest && Date > Users[User] || \
  331.     !Newest && Date < Users[User]) {
  332.         Users[User] = Date
  333.         UserFiles[User] = File
  334.     }
  335.     }
  336. }
  337.  
  338. # Put a list of login shells (from /etc/shells) into set LoginShells[].
  339. # Returns -1 if /etc/shells could not be read, else the number of shells found.
  340. function ReadShells(LoginShells,  ret,Num,Line) {
  341.     while (ret = ((getline Line < "/etc/shells") == 1))
  342.     if (Line ~ "^/") {
  343.         Num++
  344.         sub(/[ \t]+/,"",Line)
  345.         LoginShells[Line]
  346.     }
  347.     close("/etc/shells")
  348.     return ret ? -1 : Num
  349. }
  350.  
  351. # UserTags[] gives the user names to be reporting on, with the value being
  352. # the tag for the user if given.
  353. # Users[username] gives the date, as a UNIX day, since the account
  354. # was created or last logged into
  355. # Header: Header line to be printed.
  356. # TagSort: True if users should be sorted by tag value instead of time.
  357. # Brief: Print only user names.
  358. # Global vars: Debug
  359. function Report(UserTags,Users,Header,TagSort,Brief,CurDay,
  360. k,User,TagLen,Format,i,TagField,Num,oldest,elem) {
  361.     if (!Brief)
  362.     print Header
  363.     if ("OLDEST" in Users) {
  364.     oldest = Users["OLDEST"]
  365.     delete Users["OLDEST"]    # make NotEmpty(Users) work
  366.     }
  367.     if (!NotEmpty(Users)) {
  368.     if (!Brief)
  369.         if (!split(oldest,elem,";"))
  370.         print "None."
  371.         else
  372.         printf "None.  Oldest user is %s; age is %d days.\n",elem[2],
  373.         CurDay - elem[1]
  374.     return
  375.     }
  376.  
  377.     # Accumulate longest tag length, for later formatting.
  378.     for (i in UserTags)
  379.     if (length(UserTags[i]) > TagLen) {
  380.         TagLen = length(UserTags[i])
  381.         if (Debug)
  382.         printf "Tag for %s is <%s>\n",i,UserTags[i] > "/dev/stderr"
  383.     }
  384.  
  385.     # Build sort keys
  386.     if (TagSort) {
  387.     for (i in UserTags)
  388.         if (!(i in Users))
  389.         delete UserTags[i]
  390.     Num = qsortArbIndByValue(UserTags,k)
  391.     }
  392.     else
  393.     Num = qsortArbIndByValue(Users,k)
  394.  
  395.     if (TagLen)
  396.     TagField = "%-" TagLen "s  "
  397.     Format = "%-8s  " TagField "%s  %s\n"
  398.     for (i = 1; i <= Num; i++) {
  399.     User = k[i]
  400.     if (Brief)
  401.         print User
  402.     else {
  403.         if (getpwnam(User,PWEnt)) {
  404.         if (TagLen)
  405.             printf Format,User,UserTags[User],
  406.             day2date(Users[User]),PWEnt[PW_GCOS]
  407.         else
  408.             printf Format,User,day2date(Users[User]),PWEnt[PW_GCOS]
  409.         }
  410.         else
  411.         printf \
  412.         "Error: user %s not found in passwd database.\n",
  413.         User > "/dev/stderr"
  414.     }
  415.     }
  416. }
  417.  
  418. # The creation date for each account that is an index of NLUsers[]
  419. # is determined & stored (as a epoch day) as the value for the index.
  420. # The time that gives the date that a never-logged-into account was created is
  421. # determined from the timestamp on one of the user's shell rcfiles.  The
  422. # timestamp on the home directory is not used because it is touched whenever a
  423. # filesystem restore is done, etc.
  424. # Global vars: Debug
  425. # Return value: the timestamp of the oldest account found (as an epoch day)
  426. # and the owner of the account, separated by a semicolon.
  427. function FindWhenCreated(Days,NLUsers,MinDate,
  428. File,User,HDNotFound,i,Num,oldest,UsersNotFound,ShellFiles,UserFiles,
  429. oldestLogin) {
  430.     if (Debug)
  431.     printf "FindWhenCreated(): Days = %d\n",NeverDays,
  432.     LongDays > "/dev/stderr"
  433.     ShellFiles["~/.profile"]
  434.     ShellFiles["~/.zprofile"]
  435.     ShellFiles["~/.bash_profile"]
  436.     ShellFiles["~/.login"]
  437.     GetUserTimestamps(NLUsers,ShellFiles,Oldest,UserFiles)
  438.     for (User in NLUsers) {
  439.     Date = NLUsers[User]
  440.     if (Date == "") {    # No rcfiles
  441.         UsersNotFound[User]
  442.         delete NLUsers[User]
  443.         continue
  444.     }
  445.     if (oldestLogin == "" || Date < oldestLogin) {
  446.         oldestLogin = NLUsers[User]
  447.         oldest = oldestLogin ";" User
  448.     }
  449.     if (Debug > 5)
  450.         printf "timestamp for %8s: %d; file used: %s\n",
  451.         User,NLUsers[User],UserFiles[User] > "/dev/stderr"
  452.     if (Date > MinDate)    # Created more recently than cutoff date
  453.         delete NLUsers[User]
  454.     }
  455.     if (NotEmpty(UsersNotFound)) {
  456.     printf "No shell rcfiles found for these users:\n" > "/dev/stderr"
  457.     for (i in UsersNotFound)
  458.         print i > "/dev/stderr"
  459.     }
  460.     return oldest
  461. }
  462.  
  463. # GetFileDates: Get the timestamp of files, as UNIX epoch date.
  464. # Files[] is an array whose indices give the files to check.
  465. # Files that are not found are made indices of NotFound[].
  466. # The modification time of each file found is stored under its index in Files[]
  467. # (this will fail if a filename contained wildcards and is expanded by the
  468. # shell).
  469. # Globals:
  470. # Month2Num[] must be set up
  471. # If MAXEXECARGS is set, it is the maximum length of a command line.
  472. # Otherwise, the default of 1000 is used.
  473. # Debug turns on debugging if true.
  474. function GetFileDates(Files,NotFound,
  475. Month,uday,LFiles,Ind,E,Year,File,LOut,i,oldest,MaxArgs,len,Cmd) {
  476.     oldest = ""
  477.     Ind = 0
  478.     # Do l -d in case a file is a dir
  479.     Cmd = "exec l -d"
  480.     MaxArgs = ((MAXEXECARGS > 0) ? MAXEXECARGS : 1000) - length(Cmd) - 5
  481.     len = 0
  482.     for (File in Files) {
  483.     # Avoid exceeding command line length limit by doing l in batches
  484.     len += length(File) + 1
  485.     # Always put at least one file on command line, even if its length is
  486.     # greater than MaxArgs
  487.     if (len > MaxArgs && LFiles != "") {
  488.         Ind = DoCmd(Cmd LFiles " 2>&1",Ind + 1,LOut)
  489.         LFiles = " " File
  490.         len = length(LFiles)
  491.     }
  492.     else
  493.         LFiles = LFiles " " File
  494.     }
  495.     if (LFiles != "")
  496.     Ind = DoCmd(Cmd LFiles " 2>&1 || :",Ind + 1,LOut)
  497.  
  498.     for (i in LOut) {
  499.     if (split(LOut[i],E," +") < 3) {
  500.         if (Debug)
  501.         printf "i" > "/dev/stderr"
  502.         continue
  503.     }
  504.     # Put the names of files that do not exist in NotFound[]
  505.     # 3.2v4 'l' says 'foo not found'; 3.2v5 'l' says
  506.     # 'l: foo not found: No such file or directory (error 2)'
  507.     if (E[3] == "found" || E[4] == "found:") {
  508.         if (Debug)
  509.         printf "n" > "/dev/stderr"
  510.         NotFound[E[E[3] == "found" ? 1 : 2]]
  511.         continue
  512.     }
  513. #l output formats:
  514. #-r--------   1 root     auth           0 Mar 18 16:08 /.lastlogin
  515. #-r--------   1 caity    auth           0 Sep 07  1992 /u/caity/.lastlogin
  516.     # Allow a day of -1; epoch time 0 may result in that due to timezone
  517.     # differences.
  518.     if ((uday = lDate2unixday(E[6],E[7],E[8])) < -1) {
  519.         printf \
  520.         "Error: Bad date conversion of <%s,%s,%s> (result: %d) from:\n%s\n",
  521.         E[6],E[7],E[8],uday,LOut[i] > "/dev/stderr"
  522.         continue
  523.     }
  524.     File = E[9]
  525.     if (!(File in Files)) {
  526.         printf \
  527.         "Error: Unknown filename <%s> in:\n%s\n",
  528.         File,LOut[i] > "/dev/stderr"
  529.         continue
  530.     }
  531.     Files[File] = uday
  532.     if (uday < 1 && Debug)
  533.         printf "Suspiciously old file: Epoch day = %d.  l line:\n%s\n",
  534.         uday,LOut > "/dev/stderr"
  535.     }
  536.     if (Debug)
  537.     print "" > "/dev/stderr"
  538. }
  539.  
  540. function DoCmd(Cmd,i,Arr,  oi) {
  541.     Cmd = Cmd "; exit 0"    # gawk complains about this
  542.     oi = i
  543.     while ((Cmd | getline Arr[i++]) == 1)
  544.     ;
  545.     close(Cmd)
  546.     if (Debug)
  547.     printf "Got %d lines (tot %3i) from %s\n",i - oi,i-1,
  548.     substr(Cmd,1,50) > "/dev/stderr"
  549.     return i - 1
  550. }
  551.  
  552. # Make Months[n] be the name of month number n (starting with 0)
  553. # and Month2Num[Month-Abbrev] the number of the month with 3-letter
  554. # abbreviation Month-Abbrev.
  555. function MkMonth2Num(  Month) {
  556.     split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",Months,",")
  557.     for (Month in Months)
  558.     Month2Num[Months[Month]] = sprintf("%02d",Month)
  559. }
  560.  
  561. function abort(s) {
  562.     print s
  563.     exit(1)
  564. }
  565.  
  566. ### Begin date-days routines
  567. # @(#) date-days 1.1 95/08/26
  568.  
  569. # YMD2day(year,month,day-of-month) returns the number of days that passed from
  570. # 1970 Jan 1 to the given date.
  571. # All parameters should be given in numeric form.
  572. # If year < 70, it is assumed to be part of the 2000 century
  573. # If year in (70..99), 1900.
  574. # Globals: sets and uses MDays[]
  575. # Returns negative value on error.
  576. function YMD2day(Year,Month,Day,   LeapDays) {
  577.     if (Month !~ /^[0-9]+$/ || Day !~ /^[0-9]+$/)
  578.     return -1
  579.     Year+=0
  580.     Month+=0
  581.     Day+=0
  582.     if (Month <= 0 || Day <= 0 || Year < 0 || Day > 31 || Month > 12)
  583.     return -2
  584.     if (Year < 70)
  585.     Year += 100
  586.     else if (Year >= 100)
  587.     Year -= 1900
  588.     # Year is now the number of years since 1900.
  589.     LeapDays = int((Year - 68) / 4)
  590.     if (Month <= 2 && Year % 4 == 0)
  591.     LeapDays -= 1
  592.     if (!(0 in MDays))
  593.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  594.     return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
  595. }
  596.  
  597. # date2day("yy/mm/dd") returns the number of days that passed from
  598. # 1970 Jan 1 to the given date.  -1 is returned on error.
  599. # The fields are returned in Fields: year in Fields[1], month in Fields[2],
  600. # and day (if given) in Fields[3].
  601. function date2day(Date,Fields,  Num,Year,Month) {
  602.     Num = split(Date,Fields,"/")
  603.     if (Num != 2 && Num != 3)
  604.     return -1
  605.     if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
  606.     return -1
  607.     if (Num == 3)
  608.     Day = Fields[3]
  609.     return YMD2day(Year,Month,Day)
  610. }
  611.  
  612. # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
  613. # returns the number of complete days that passed from date 1 to date 2
  614. function diffdays(year1,month1,day1,year2,month2,day2) {
  615.     return date2days(year2,month2,day2) - date2days(year1,month1,day1)
  616. }
  617.  
  618. # Given an epoch month, return the first day of that month
  619. function month2day(Month) {
  620.     return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
  621. }
  622.  
  623. # Given an epoch day, returns epoch month
  624. function day2month(Day,  Date) {
  625.     day2YMD(Day,Date)
  626.     return (Date["y"]-1970)*12 + Date["m"]-1
  627. }
  628.  
  629. # Given an epoch month, returns the number of days in that month.
  630. function monthdays(month,  year) {
  631.     if (!(0 in MDur))
  632.     split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
  633.     year = int(month/12)
  634.     month = month%12+1
  635.     return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
  636. }
  637.  
  638. # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.),
  639. # returns the date elements in Date:
  640. # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
  641. # Date["d"] = day of month.
  642. # Globals: Sets/uses MDays[].
  643. function day2YMD(Day,Date,  QYears,Year,NonLeapYears,Month) {
  644.     if (!(0 in LDays)) {
  645.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  646.     split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
  647.     }
  648.     Day += 365
  649.     # Day is now # of days since Jan 1 1969.  1968 was a leap year.
  650.     QYears = int(Day / (365*4+1))
  651.     Year = 1969 + QYears * 4
  652.     Day -= QYears * (365*4+1)
  653.     # Day now contains no complete leap years.
  654.     Year += NonLeapYears = int(Day/365)
  655.     Leap = !(Year % 4)
  656.     Day -= NonLeapYears * 365
  657.     # Day now contains the day of year.
  658.     # Find the month.  Divide day by 32 to get either the correct month or
  659.     # the month prior to it.
  660.     Month = int(Day++ / 32) + 1
  661.     if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
  662.     Month++
  663.     Day -= Leap ? LDays[Month] : MDays[Month]
  664.     Date["d"] = Day
  665.     Date["m"] = Month
  666.     Date["y"] = Year
  667. }
  668.  
  669. # Given a month number, return a date in the form yy/mm
  670. function month2date(MonthNum) {
  671.     return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
  672. }
  673.  
  674. # Given a day number, return a date in the form yy/mm/dd
  675. function day2date(day,  Date) {
  676.     day2YMD(day,Date)
  677.     return sprintf("%02d/%02d/%02d",Date["y"]%100,Date["m"],Date["d"])
  678. }
  679.  
  680. function lDate2unixday(Mon,Day,Year,  Month) {
  681.     if (!(1 in Month2Num)) {
  682.     MkMonth2Num()
  683.     CurYear = strftime("%y")
  684.     CurMonth = strftime("%m")
  685.     }
  686.     Month = Month2Num[Mon]
  687.     # Deal with varying dates printed by l
  688.     # Use year if given
  689.     # Subtract 1 from year if month given is from last year
  690.     if (Year ~ ":")    # If year is actually time...
  691.     Year = (CurYear - (Month > CurMonth)) % 100
  692.     return YMD2day(Year,Month,Day)
  693. }
  694.  
  695. ### End date-days routines
  696.  
  697. function NotEmpty(Arr,  i) {
  698.     for (i in Arr)
  699.     break
  700.     return (i in Arr)
  701. }
  702.  
  703. ### Start of ProcArgs library
  704. # @(#) ProcArgs 1.11 96/12/08
  705. # 92/02/29 john h. dubois iii (john@armory.com)
  706. # 93/07/18 Added "#" arg type
  707. # 93/09/26 Do not count -h against MinArgs
  708. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  709. #          Removed meaning of "+" or "-" by itself.
  710. # 94/03/08 Added & option and *()< option types.
  711. # 94/04/02 Added NoRCopt to Opts()
  712. # 94/06/11 Mark numeric variables as such.
  713. # 94/07/08 Opts(): Do not require any args if h option is given.
  714. # 95/01/22 Record options given more than once.  Record option num in argv.
  715. # 95/06/08 Added ExclusiveOptions().
  716. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  717. #          Expand $VARNAME at the start of its filenames.
  718. #          Let varname=0 and -option- turn off an option.
  719. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  720. #          of the vars should be searched for in the environment.
  721. #          Check for duplicate rcfiles.
  722. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  723. #          now return various negatives values on error, not just -1, and
  724. #          Opts() may set Err to various positive values, not just 1.
  725. #          Added AllowUnrecOpt.
  726. # 96/05/23 Check type given for & option
  727. # 96/06/15 Re-port to awk
  728. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  729. #          used by other functions.
  730. # 96/10/15 Added OptChars
  731. # 96/11/01 Added exOpts arg to Opts()
  732. # 96/11/16 Added ; type
  733. # 96/12/08 Added Opt2Set() & Opt2Sets()
  734. # 96/12/27 Added CmdLineOpt()
  735.  
  736. # optlist is a string which contains all of the possible command line options.
  737. # A character followed by certain characters indicates that the option takes
  738. # an argument, with type as follows:
  739. # :    String argument
  740. # ;    Non-empty string argument
  741. # *    Floating point argument
  742. # (    Non-negative floating point argument
  743. # )    Positive floating point argument
  744. # #    Integer argument
  745. # <    Non-negative integer argument
  746. # >    Positive integer argument
  747. # The only difference the type of argument makes is in the runtime argument
  748. # error checking that is done.
  749.  
  750. # The & option is a special case used to get numeric options without the
  751. # user having to give an option character.  It is shorthand for [-+.0-9].
  752. # If & is included in optlist and an option string that begins with one of
  753. # these characters is seen, the value given to "&" will include the first
  754. # char of the option.  & must be followed by a type character other than ":"
  755. # or ";".
  756. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  757.  
  758. # Strings in argv[] which begin with "-" or "+" are taken to be
  759. # strings of options, except that a string which consists solely of "-"
  760. # or "+" is taken to be a non-option string; like other non-option strings,
  761. # it stops the scanning of argv and is left in argv[].
  762. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  763. # If an option takes an argument, the argument may either immediately
  764. # follow it or be given separately.
  765. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  766. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  767. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  768. # this feature had a flaw that caused problems in some cases.  See the OptChars
  769. # parameter to explicitly set the option-specifier characters.
  770.  
  771. # If an option that does not take an argument is given,
  772. # an index with its name is created in Options and its value is set to the
  773. # number of times it occurs in argv[].
  774.  
  775. # If an option that does take an argument is given, an index with its name is
  776. # created in Options and its value is set to the value of the argument given
  777. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  778. # If an option that takes an argument is given more than once,
  779. # Options[option-name,"count"] is incremented, and the value is assigned to
  780. # the index (option-name,instance) where instance is 2 for the second occurance
  781. # of the option, etc.
  782. # In other words, the first time an option with a value is encountered, the
  783. # value is assigned to an index consisting only of its name; for any further
  784. # occurances of the option, the value index has an extra (count) dimension.
  785.  
  786. # The sequence number for each option found in argv[] is stored in
  787. # Options[option-name,"num",instance], where instance is 1 for the first
  788. # occurance of the option, etc.  The sequence number starts at 1 and is
  789. # incremented for each option, both those that have a value and those that
  790. # do not.  Options set from a config file have a value of 0 assigned to this.
  791.  
  792. # Options and their arguments are deleted from argv.
  793. # Note that this means that there may be gaps left in the indices of argv[].
  794. # If compress is nonzero, argv[] is packed by moving its elements so that
  795. # they have contiguous integer indices starting with 0.
  796. # Option processing will stop with the first unrecognized option, just as
  797. # though -- was given except that unlike -- the unrecognized option will not be
  798. # removed from ARGV[].  Normally, an error value is returned in this case.
  799. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  800. # be found, so the number of remaining arguments is returned instead.
  801. # If OptChars is not a null string, it is the set of characters that indicate
  802. # that an argument is an option string if the string begins with one of the
  803. # characters.  A string consisting solely of two of the same option-indicator
  804. # characters stops the scanning of argv[].  The default is "-+".
  805. # argv[0] is not examined.
  806. # The number of arguments left in argc is returned.
  807. # If an error occurs, the global string OptErr is set to an error message
  808. # and a negative value is returned.
  809. # Current error values:
  810. # -1: option that required an argument did not get it.
  811. # -2: argument of incorrect type supplied for an option.
  812. # -3: unrecognized (invalid) option.
  813. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  814. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  815. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  816. {
  817. # ArgNum is the index of the argument being processed.
  818. # ArgsLeft is the number of arguments left in argv.
  819. # Arg is the argument being processed.
  820. # ArgLen is the length of the argument being processed.
  821. # ArgInd is the position of the character in Arg being processed.
  822. # Option is the character in Arg being processed.
  823. # Pos is the position in OptList of the option being processed.
  824. # NumOpt is true if a numeric option may be given.
  825.     ArgsLeft = argc
  826.     NumOpt = index(OptList,"&")
  827.     OptionNum = 0
  828.     if (OptChars == "")
  829.     OptChars = "-+"
  830.     while (OptChars != "") {
  831.     c = substr(OptChars,1,1)
  832.     OptChars = substr(OptChars,2)
  833.     OptCharSet[c]
  834.     OptTerm[c c]
  835.     }
  836.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  837.     Arg = argv[ArgNum]
  838.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  839.         break    # Not an option; quit
  840.     if (Arg in OptTerm) {
  841.         delete argv[ArgNum]
  842.         ArgsLeft--
  843.         break
  844.     }
  845.     ArgLen = length(Arg)
  846.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  847.         Option = substr(Arg,ArgInd,1)
  848.         if (NumOpt && Option ~ /[-+.0-9]/) {
  849.         # If this option is a numeric option, make its flag be & and
  850.         # its option string flag position be the position of & in
  851.         # the option string.
  852.         Option = "&"
  853.         Pos = NumOpt
  854.         # Prefix Arg with a char so that ArgInd will point to the
  855.         # first char of the numeric option.
  856.         Arg = "&" Arg
  857.         ArgLen++
  858.         }
  859.         # Find position of flag in option string, to get its type (if any).
  860.         # Disallow & as literal flag.
  861.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  862.         if (AllowUnrecOpt) {
  863.             Escape = 1
  864.             break
  865.         }
  866.         else {
  867.             OptErr = "Invalid option: " specGiven Option
  868.             return -3
  869.         }
  870.         }
  871.  
  872.         # Find what the value of the option will be if it takes one.
  873.         # NeedNextOpt is true if the option specifier is the last char of
  874.         # this arg, which means that if the option requires a value it is
  875.         # the next arg.
  876.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  877.         if (GotValue = ArgNum + 1 < argc)
  878.             Value = argv[ArgNum+1]
  879.         }
  880.         else {    # Value is included with option
  881.         Value = substr(Arg,ArgInd + 1)
  882.         GotValue = 1
  883.         }
  884.  
  885.         if (HadValue = AssignVal(Option,Value,Options,
  886.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  887.         specGiven)) {
  888.         if (HadValue < 0)    # error occured
  889.             return HadValue
  890.         if (HadValue == 2)
  891.             ArgInd++    # Account for the single-char value we used.
  892.         else {
  893.             if (NeedNextOpt) {    # option took next arg as value
  894.             delete argv[++ArgNum]
  895.             ArgsLeft--
  896.             }
  897.             break    # This option has been used up
  898.         }
  899.         }
  900.     }
  901.     if (Escape)
  902.         break
  903.     # Do not delete arg until after processing of it, so that if it is not
  904.     # recognized it can be left in ARGV[].
  905.     delete argv[ArgNum]
  906.     ArgsLeft--
  907.     }
  908.     if (compress != 0) {
  909.     dest = 1
  910.     src = argc - ArgsLeft + 1
  911.     for (count = ArgsLeft - 1; count; count--) {
  912.         ARGV[dest] = ARGV[src]
  913.         dest++
  914.         src++
  915.     }
  916.     }
  917.     return ArgsLeft
  918. }
  919.  
  920. # Assignment to values in Options[] occurs only in this function.
  921. # Option: Option specifier character.
  922. # Value: Value to be assigned to option, if it takes a value.
  923. # Options[]: Options array to return values in.
  924. # ArgType: Argument type specifier character.
  925. # GotValue: Whether any value is available to be assigned to this option.
  926. # Name: Name of option being processed.
  927. # OptionNum: Number of this option (starting with 1) if set in argv[],
  928. #     or 0 if it was given in a config file or in the environment.
  929. # SingleOpt: true if the value (if any) that is available for this option was
  930. #     given as part of the same command line arg as the option.  Used only for
  931. #     options from the command line.
  932. # specGiven is the option specifier character use, if any (e.g. - or +),
  933. # for use in error messages.
  934. # Global variables: OptErr
  935. # Return value: negative value on error, 0 if option did not require an
  936. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  937. # the arg.
  938. # Current error values:
  939. # -1: Option that required an argument did not get it.
  940. # -2: Value of incorrect type supplied for option.
  941. # -3: Bad type given for option &
  942. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  943. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  944.     # If option takes a value...    [
  945.     NumTypes = "*()#<>]"
  946.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  947.     OptErr = "Bad type given for & option"
  948.     return -3
  949.     }
  950.  
  951.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  952.     if (!GotValue) {
  953.         if (Name != "")
  954.         OptErr = "Variable requires a value -- " Name
  955.         else
  956.         OptErr = "option requires an argument -- " Option
  957.         return -1
  958.     }
  959.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  960.         OptErr = Err
  961.         return -2
  962.     }
  963.     # Mark this as a numeric variable; will be propogated to Options[] val.
  964.     if (ArgType != ":" && ArgType != ";")
  965.         Value += 0
  966.     if ((Instance = ++Options[Option,"count"]) > 1)
  967.         Options[Option,Instance] = Value
  968.     else
  969.         Options[Option] = Value
  970.     }
  971.     # If this is an environ or rcfile assignment & it was given a value...
  972.     else if (!OptionNum && Value != "") {
  973.     UsedValue = 1
  974.     # If the value is "0" or "-" and this is the first instance of it,
  975.     # do not set Options[Option]; this allows an assignment in an rcfile to
  976.     # turn off an option (for the simple "Option in Options" test) in such
  977.     # a way that it cannot be turned on in a later file.
  978.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  979.         Instance = 1
  980.     else
  981.         Instance = ++Options[Option]
  982.     # Save the value even though this is a flag
  983.     Options[Option,Instance] = Value
  984.     }
  985.     # If this is a command line flag and has a - following it in the same arg,
  986.     # it is being turned off.
  987.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  988.     UsedValue = 2
  989.     if (Option in Options)
  990.         Instance = ++Options[Option]
  991.     else
  992.         Instance = 1
  993.     Options[Option,Instance]
  994.     }
  995.     # If this is a flag assignment without a value, increment the count for the
  996.     # flag unless it was turned off.  The indicator for a flag being turned off
  997.     # is that the flag index has not been set in Options[] but it has an
  998.     # instance count.
  999.     else if (Option in Options || !((Option,1) in Options))
  1000.     # Increment number of times this flag seen; will inc null value to 1
  1001.     Instance = ++Options[Option]
  1002.     Options[Option,"num",Instance] = OptionNum
  1003.     return UsedValue
  1004. }
  1005.  
  1006. # Option is the option letter
  1007. # Value is the value being assigned
  1008. # Name is the var name of the option, if any
  1009. # ArgType is one of:
  1010. # :    String argument
  1011. # ;    Non-null string argument
  1012. # *    Floating point argument
  1013. # (    Non-negative floating point argument
  1014. # )    Positive floating point argument
  1015. # #    Integer argument
  1016. # <    Non-negative integer argument
  1017. # >    Positive integer argument
  1018. # specGiven is the option specifier character use, if any (e.g. - or +),
  1019. # for use in error messages.
  1020. # Returns null on success, err string on error
  1021. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1022.     if (ArgType == ":")
  1023.     return ""
  1024.     if (ArgType == ";") {
  1025.     if (Value == "")
  1026.         Err = "must be a non-empty string"
  1027.     }
  1028.     # A number begins with optional + or -, and is followed by a string of
  1029.     # digits or a decimal with digits before it, after it, or both
  1030.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1031.     Err = "must be a number"
  1032.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1033.     Err = "may not include a fraction"
  1034.     else if (ArgType ~ "[()<>]" && Value < 0)
  1035.     Err = "may not be negative"
  1036.     # (
  1037.     else if (ArgType ~ "[)>]" && Value == 0)
  1038.     Err = "must be a positive number"
  1039.     if (Err != "") {
  1040.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1041.     if (Name != "")
  1042.         return ErrStr "variable " substr(Name,1,1) " " Err
  1043.     else {
  1044.         if (Option == "&")
  1045.         Option = Value
  1046.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1047.     }
  1048.     }
  1049.     else
  1050.     return ""
  1051. }
  1052.  
  1053. # Note: only the above functions are needed by ProcArgs.
  1054. # The rest of these functions call ProcArgs() and also do other
  1055. # option-processing stuff.
  1056.  
  1057. # Opts: Process command line arguments.
  1058. # Opts processes command line arguments using ProcArgs()
  1059. # and checks for errors.  If an error occurs, a message is printed
  1060. # and the program is exited.
  1061. #
  1062. # Input variables:
  1063. # Name is the name of the program, for error messages.
  1064. # Usage is a usage message, for error messages.
  1065. # OptList the option description string, as used by ProcArgs().
  1066. # MinArgs is the minimum number of non-option arguments that this
  1067. # program should have, non including ARGV[0] and +h.
  1068. # If the program does not require any non-option arguments,
  1069. # MinArgs should be omitted or given as 0.
  1070. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1071. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1072. # by the value of the environment variable HOME.  If a filename begins with
  1073. # $, the part from the character after the $ up until (but not including)
  1074. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1075. # environment; if found its value will be substituted, if not the filename will
  1076. # be discarded.
  1077. # rcfiles are read in the order given.
  1078. # Values given in them will not override values given on the command line,
  1079. # and values given in later files will not override those set in earlier
  1080. # files, because AssignVal() will store each with a different instance index.
  1081. # The first instance of each variable, either on the command line or in an
  1082. # rcfile, will be stored with no instance index, and this is the value
  1083. # normally used by programs that call this function.
  1084. # VarNames is a comma-separated list of variable names to map to options,
  1085. # in the same order as the options are given in OptList.
  1086. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1087. # searched for in the environment.  If set to -1, all values will be searched
  1088. # for in the environment.  Values given in the environment will override
  1089. # those given in the rcfiles but not those given on the command line.
  1090. # NoRCopt, if given, is an additional letter option that if given on the
  1091. # command line prevents the rcfiles from being read.
  1092. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1093. # ExclusiveOptions() for a description of exOpts.
  1094. # Special options:
  1095. # If x is made an option and is given, some debugging info is output.
  1096. # h is assumed to be the help option.
  1097.  
  1098. # Global variables:
  1099. # The command line arguments are taken from ARGV[].
  1100. # The arguments that are option specifiers and values are removed from
  1101. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1102. # The number of elements in ARGV[] should be in ARGC.
  1103. # After processing, ARGC is set to the number of elements left in ARGV[].
  1104. # The option values are put in Options[].
  1105. # On error, Err is set to a positive integer value so it can be checked for in
  1106. # an END block.
  1107. # Return value: The number of elements left in ARGV is returned.
  1108. # Must keep OptErr global since it may be set by InitOpts().
  1109. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1110. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1111.     if (MinArgs == "")
  1112.     MinArgs = 0
  1113.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1114.     optChars)
  1115.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1116.     if (ArgsLeft >= 0) {
  1117.         OptErr = "Not enough arguments"
  1118.         Err = 4
  1119.     }
  1120.     else
  1121.         Err = -ArgsLeft
  1122.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1123.     Name,OptErr,Usage > "/dev/stderr"
  1124.     exit 1
  1125.     }
  1126.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1127.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1128.     {
  1129.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1130.     Err = -e
  1131.     exit 1
  1132.     }
  1133.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1134.     {
  1135.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1136.     Err = 1
  1137.     exit 1
  1138.     }
  1139.     return ArgsLeft
  1140. }
  1141.  
  1142. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1143. # <variable-name><assignment-char><value>.
  1144. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1145. # line and whitespace between the variable name and the assignment character)
  1146. # is stripped.  Lines that do not contain an assignment operator or which
  1147. # contain a null variable name are ignored, other than possibly being noted in
  1148. # the return value.  If more than one assignment is made to a variable, the
  1149. # first assignment is used.
  1150. # Input variables:
  1151. # File is the file to read.
  1152. # Comment is the line-comment character.  If it is found as the first non-
  1153. #     whitespace character on a line, the line is ignored.
  1154. # Assign is the assignment string.  The first instance of Assign on a line
  1155. #     separates the variable name from its value.
  1156. # If StripWhite is true, whitespace around the value (whitespace between the
  1157. #     assignment char and trailing whitespace on the line) is stripped.
  1158. # VarPat is a pattern that variable names must match.
  1159. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1160. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1161. #     a line; no assignment operator is needed.  These variables are set in
  1162. #     the output array with a null value.  Lines containing nothing but
  1163. #     whitespace are still ignored.
  1164. # Output variables:
  1165. # Values[] contains the assignments, with the indexes being the variable names
  1166. #     and the values being the assigned values.
  1167. # Lines[] contains the line number that each variable occured on.  A flag set
  1168. #     is record by giving it an index in Lines[] but not in Values[].
  1169. # Return value:
  1170. # If any errors occur, a string consisting of descriptions of the errors
  1171. # separated by newlines is returned.  In no case will the string start with a
  1172. # numeric value.  If no errors occur,  the number of lines read is returned.
  1173. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1174. FlagsOK,
  1175. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1176.     if (Comment != "")
  1177.     Comment = "^" Comment
  1178.     AssignLen = length(Assign)
  1179.     if (VarPat == "")
  1180.     VarPat = "."    # null varname not allowed
  1181.     while ((Status = (getline Line < File)) == 1) {
  1182.     LineNum++
  1183.     sub("^[ \t]+","",Line)
  1184.     if (Line == "")        # blank line
  1185.         continue
  1186.     if (Comment != "" && Line ~ Comment)
  1187.         continue
  1188.     if (Pos = index(Line,Assign)) {
  1189.         Var = substr(Line,1,Pos-1)
  1190.         Val = substr(Line,Pos+AssignLen)
  1191.         if (StripWhite) {
  1192.         sub("^[ \t]+","",Val)
  1193.         sub("[ \t]+$","",Val)
  1194.         }
  1195.     }
  1196.     else {
  1197.         Var = Line    # If no value, var is entire line
  1198.         Val = ""
  1199.     }
  1200.     if (!FlagsOK && Val == "") {
  1201.         Errs = Errs \
  1202.         sprintf("\nBad assignment on line %d of file %s: %s",
  1203.         LineNum,File,Line)
  1204.         continue
  1205.     }
  1206.     sub("[ \t]+$","",Var)
  1207.     if (Var !~ VarPat) {
  1208.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1209.         LineNum,File,Var)
  1210.         continue
  1211.     }
  1212.     if (!(Var in Lines)) {
  1213.         Lines[Var] = LineNum
  1214.         if (Pos)
  1215.         Values[Var] = Val
  1216.     }
  1217.     }
  1218.     if (Status)
  1219.     Errs = Errs "\nCould not read file " File
  1220.     close(File)
  1221.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1222. }
  1223.  
  1224. # Variables:
  1225. # Data is stored in Options[].
  1226. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1227. # Global vars:
  1228. # Sets OptErr.  Uses ENVIRON[].
  1229. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1230. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1231. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1232. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1233.     split("",filesRead,"")    # make awk know this is an array
  1234.     NumVars = split(VarNames,Vars,",")
  1235.     TypesInd = Ret = 0
  1236.     if (EnvSearch == -1)
  1237.     EnvSearch = NumVars
  1238.     for (i = 1; i <= NumVars; i++) {
  1239.     Var = Vars[i]
  1240.     CharOpt = substr(OptList,++TypesInd,1)
  1241.     if (CharOpt ~ "^[:;*()#<>&]$")
  1242.         CharOpt = substr(OptList,++TypesInd,1)
  1243.     Map[Var] = CharOpt
  1244.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1245.     # Do not overwrite entries from environment
  1246.     if (i <= EnvSearch && Var in ENVIRON &&
  1247.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1248.         return Err
  1249.     }
  1250.  
  1251.     numrcFiles = split(rcFiles,fNames,":")
  1252.     for (i = 1; i <= numrcFiles; i++) {
  1253.     rcFile = fNames[i]
  1254.     if (rcFile ~ "^~/")
  1255.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1256.     else if (rcFile ~ /^\$/) {
  1257.         rcFile = substr(rcFile,2)
  1258.         match(rcFile,"^[a-zA-Z0-9_]*")
  1259.         envvar = substr(rcFile,1,RLENGTH)
  1260.         if (envvar in ENVIRON)
  1261.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1262.         else
  1263.         continue
  1264.     }
  1265.     if (rcFile in filesRead)
  1266.         continue
  1267.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1268.     # may be the same
  1269.     filesRead[rcFile]
  1270.     if ("x" in Options)
  1271.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1272.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1273.     if (retStr > 0)
  1274.         READ_RCFILE = 1
  1275.     else if (ret != "") {
  1276.         OptErr = retStr
  1277.         Ret = -1
  1278.     }
  1279.     for (Var in Lines)
  1280.         if (Var in Map) {
  1281.         if ((Err = AssignVal(Map[Var],
  1282.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1283.         Var in Values,Var,0)) < 0)
  1284.             return Err
  1285.         }
  1286.         else {
  1287.         OptErr = sprintf(\
  1288.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1289.         Lines[Var],rcFile)
  1290.         Ret = -1
  1291.         }
  1292.     }
  1293.  
  1294.     if ("x" in Options)
  1295.     for (Var in Map)
  1296.         if (Map[Var] in Options)
  1297.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1298.         "/dev/stderr"
  1299.         else
  1300.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1301.     return Ret
  1302. }
  1303.  
  1304. # OptSets is a semicolon-separated list of sets of option sets.
  1305. # Within a list of option sets, the option sets are separated by commas.  For
  1306. # each set of sets, if any option in one of the sets is in Options[] AND any
  1307. # option in one of the other sets is in Options[], an error string is returned.
  1308. # If no conflicts are found, nothing is returned.
  1309. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1310. # the exclusions presented by the first set of sets (ab,def,g) if:
  1311. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1312. # (a or b is in Options[]) AND (g is in Options) OR
  1313. # (d, e, or f is in Options[]) AND (g is in Options)
  1314. # An error will be returned due to the exclusions presented by the second set
  1315. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1316. # todo: make options given on command line unset options given in config file
  1317. # todo: that they conflict with.
  1318. function ExclusiveOptions(OptSets,Options,
  1319. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1320. SetNum,OSetNum) {
  1321.     NumSetSets = split(OptSets,SetSets,";")
  1322.     # For each set of sets...
  1323.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1324.     # NumSets is the number of sets in this set of sets.
  1325.     NumSets = split(SetSets[SetSet],Sets,",")
  1326.     # For each set in a set of sets except the last...
  1327.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1328.         s1 = Sets[SetNum]
  1329.         L1 = length(s1)
  1330.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1331.         # If any of the options in this set was given, check whether
  1332.         # any of the options in the other sets was given.  Only check
  1333.         # later sets since earlier sets will have already been checked
  1334.         # against this set.
  1335.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1336.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1337.             s2 = Sets[OSetNum]
  1338.             L2 = length(s2)
  1339.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1340.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1341.                 ErrStr = ErrStr "\n"\
  1342.                 sprintf("Cannot give both %s and %s options.",
  1343.                 c1,c2)
  1344.             }
  1345.     }
  1346.     }
  1347.     if (ErrStr != "")
  1348.     return substr(ErrStr,2)
  1349.     return ""
  1350. }
  1351.  
  1352. # The value of each instance of option Opt that occurs in Options[] is made an
  1353. # index of Set[].
  1354. # The return value is the number of instances of Opt in Options.
  1355. function Opt2Set(Options,Opt,Set,  count) {
  1356.     if (!(Opt in Options))
  1357.     return 0
  1358.     Set[Options[Opt]]
  1359.     count = Options[Opt,"count"]
  1360.     for (; count > 1; count--)
  1361.     Set[Options[Opt,count]]
  1362.     return count
  1363. }
  1364.  
  1365. # The value of each instance of option Opt that occurs in Options[] that
  1366. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1367. # Other values are made indexes of Set[].
  1368. # The return value is the number of instances of Opt in Options.
  1369. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1370.     ret = Opt2Set(Options,Opt,aSet)
  1371.     for (value in aSet)
  1372.     if (substr(value,1,1) == "!")
  1373.         nSet[substr(value,2)]
  1374.     else
  1375.         Set[value]
  1376.     return ret
  1377. }
  1378.  
  1379. # Returns true if option Opt was given on the command line.
  1380. function CmdLineOpt(Options,Opt,  i) {
  1381.     for (i = 1; (Opt,"num",i) in Options; i++)
  1382.     if (Options[Opt,"num",i] != 0)
  1383.         return 1
  1384.     return 0
  1385. }
  1386. ### End of ProcArgs library
  1387. ### Begin pwent library
  1388.  
  1389. # @(#) pwent.awk 1.2 96/06/27
  1390. # 92/08/10 john h. dubois III (john@armory.com)
  1391. # 93/12/13 fixed to not clobber $*
  1392. # 96/01/05 Send error messages to /dev/stderr
  1393. # 96/05/24 Let getpwnam() return a specific field if requested.
  1394. #          Added PW_REAL and PW_OFFICE.
  1395. # 96/06/03 Added Type field to getpwent()
  1396. # 96/06/24 Allow a Field to be requested for getpwent() also.
  1397. # 96/06/29 Added PW_RECORD, and getpwreal().
  1398. #          Changed PWLines to be index by record number instead of name.
  1399. # 96/11/17 Added getpwuid()
  1400.  
  1401. # Require: ReadShells()
  1402.  
  1403. # getpwent, getpwnam: get an entry from the passwd file.
  1404. # Each of the following passwd functions returns an array which contains
  1405. # a passwd file entry.  The array contains the fields of the entry.
  1406. # Global variables:
  1407. # The following variables are defined with the values of the indexes of the
  1408. # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
  1409. # PWLines[] contains the lines of the password file, indexed by record number,
  1410. # starting with 1.
  1411. # _pwNames[] is a mapping of name to passwd record number.
  1412. # getpwentNum is the number of the next entry to be returned by getpwent().
  1413.  
  1414. # Left FS global because making it local does not work in gawk.
  1415. function ReadPasswd(  User,Line,i,Ind,ret,OFS) {
  1416.     if (PW_Name)
  1417.     return 1
  1418.     PW_NAME = 1
  1419.     PW_PASSWORD = 2
  1420.     PW_UID = 3
  1421.     PW_GID = 4
  1422.     PW_GCOS = 5
  1423.     PW_HOME = 6
  1424.     PW_SHELL = 7
  1425.     PW_REAL = -1    # for PWGetFields()
  1426.     PW_OFFICE = -2
  1427.     PW_RECORD = -3
  1428.  
  1429.     Ind = getpwentNum = 1
  1430.     OFS = FS
  1431.     FS = ":"
  1432.     while ((ret = (getline Line < "/etc/passwd")) == 1) {
  1433.     User = Line
  1434.     sub(":.*","",User)
  1435.     _pwNames[User] = Ind
  1436.     PWLines[Ind++] = Line
  1437.     }
  1438.     FS = OFS
  1439.     close("/etc/passwd")
  1440.     if (ret) {
  1441.     printf "ReadPasswd(): Could not open /etc/passwd.\n" > "/dev/stderr"
  1442.     return 0
  1443.     }
  1444.     return 1
  1445. }
  1446.  
  1447. # setpwent resets the passwd file entry pointer used by getpwent
  1448. # to the first entry.
  1449. function setpwent() {
  1450.     getpwentNum = 1
  1451. }
  1452.  
  1453. # getpwent sets PWEnt to the next entry in the passwd file.
  1454. # If Type is set to -1, the entry for the next "real" user is returned (others
  1455. # are skipped over), where a real user is a user whose login shell is listed in
  1456. # /etc/shells.  This requires the ReadShells() function.  Other values for
  1457. # Type are not yet defined and are ignored.
  1458. # If the last entry has already been returned, 0 is return if Field is null,
  1459. # ":" if not.
  1460. # If the entry for the next real user has been requested and /etc/shells
  1461. # cannot be read, -1 is returned if Field is null, "\n" if not.
  1462. # See PWGetFields() for other return values and the meaning of the Field
  1463. # parameter.
  1464. function getpwent(PWEnt,Type,Field,  entNum) {
  1465.     if (!PW_NAME)
  1466.     ReadPasswd()
  1467.     if (!(getpwentNum in PWLines))
  1468.     return Field ? ":" : 0
  1469.     if (Type == -1) {
  1470.     if (!_DidReadShells && ReadShells(LoginShells) == -1)
  1471.         return Field ? "\n" : -1
  1472.     split(PWLines[getpwentNum++],PWEnt,":")
  1473.     while (!(PWEnt[PW_SHELL] in LoginShells)) {
  1474.         if (!(getpwentNum in PWLines))
  1475.         return Field ? ":" : 0
  1476.         split(PWLines[getpwentNum++],PWEnt,":")
  1477.     }
  1478.     return PWGetFields("",PWEnt,Field,getpwentNum - 1)
  1479.     }
  1480.     else {
  1481.     entNum = getpwentNum
  1482.     return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
  1483.     }
  1484. }
  1485.  
  1486. function MakeInd(  Elem,Ind,Line,uid,home) {
  1487.     for (Ind = 1; Ind in PWLines; Ind++) {
  1488.     Line = PWLines[Ind]
  1489.     split(Line,Elem,":")
  1490.     uid = Elem[PW_UID]
  1491.     if (!(uid in uidInd))
  1492.         uidInd[uid] = Ind
  1493.     home = Elem[PW_HOME]
  1494.     if (!(home in HomeInd))
  1495.         HomeInd[home] = Ind
  1496.     }
  1497.     IndDone = 1
  1498. }
  1499.  
  1500. # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
  1501. # from it.  If PWLine is null, PWEnt[] is assumed to have already been filled
  1502. # in with a password entry.
  1503. # If Field is not passed or is null, the return value is 1.
  1504. # If Field is non-null, it should a PW_ value.  In this case, the value of the
  1505. # requested field is returned.
  1506. # entNum is the value that PWEnt[PW_RECORD] should be set to.  It should be
  1507. # the index in PWLines[] of the record being processed.
  1508. # In addition to the PW_ values used by the rest of the functions in this
  1509. # library, this function can be passed PW_REAL and PW_OFFICE.
  1510. # PW_REAL will get the part of the GCOS field before the first comma.
  1511. # PW_OFFICE will get the part of the GCOS field after the first comma.
  1512. # If either of these is requested, both values will also be assigned to their
  1513. # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
  1514. # PW_OFFICE will not be set.
  1515. # NOTE: since the global field names are set in ReadShells(), it must be
  1516. # executed before any of the field name can be passed.
  1517. function PWGetFields(PWLine,PWEnt,Field,entNum,  gcos,ind) {
  1518.     if (PWLine != "")
  1519.     split(PWLine,PWEnt,":")
  1520.     PWEnt[PW_RECORD] = entNum
  1521.     if (!Field)
  1522.     return 1
  1523.     if (Field < 0) {
  1524.     if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
  1525.         PWEnt[PW_OFFICE] = substr(gcos,ind+1)
  1526.         PWEnt[PW_REAL] = substr(gcos,1,ind-1)
  1527.     }
  1528.     else
  1529.         PWEnt[PW_REAL] = gcos
  1530.     }
  1531.     return PWEnt[Field]
  1532. }
  1533.  
  1534. # getpwnam sets PWEnt to the passwd entry for login name Name.
  1535. # If Name does not exist in the password file, the return value is ":"
  1536. # if Field was passed, 0 if not.
  1537. # For other return values and parameter explanation, see PWGetFields()
  1538. function getpwnam(Name,PWEnt,Field) {
  1539.     if (!PW_NAME)
  1540.     ReadPasswd()
  1541.     if (Name in _pwNames)
  1542.     return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
  1543.     else
  1544.     return Field ? ":" : 0
  1545. }
  1546.  
  1547. # getpwhome sets PWEnt to the passwd entry whose home dir is Home.
  1548. # See getpwnam() for return values and the meaning of the Field param.
  1549. function getpwhome(Home,PWEnt,Field) {
  1550.     if (!PW_NAME)
  1551.     ReadPasswd()
  1552.     if (!IndDone)
  1553.     MakeInd()
  1554.     if (Home in HomeInd)
  1555.     return PWGetFields(PWLines[HomeInd[Home]],PWEnt,Field,HomeInd[Home])
  1556.     else
  1557.     return Field ? ":" : 0
  1558. }
  1559.  
  1560. # getpwuid sets PWEnt to the passwd entry whose uid is UID.
  1561. # See getpwnam() for return values and the meaning of the Field param.
  1562. function getpwuid(UID,PWEnt,Field) {
  1563.     if (!PW_NAME)
  1564.     ReadPasswd()
  1565.     if (!IndDone)
  1566.     MakeInd()
  1567.     if ((UID + 0) in uidInd)
  1568.     return PWGetFields(PWLines[uidInd[UID]],PWEnt,Field,uidInd[UID])
  1569.     else
  1570.     return Field ? ":" : 0
  1571. }
  1572.  
  1573. # Make an index by real name.  For each passwd file entry, the real-name
  1574. # is lowercased and split into components on non-alphanums.   The passwd entry
  1575. # index that the name came from is added to the value of each such component
  1576. # in the global _RealInd[].  The indexes stored this way are separated by
  1577. # commas.  If the real-name contains no alphanums, its index is stored under
  1578. # the null index.
  1579. function _makeRealInd(  PWEnt,ret,Elem,nelem,i,Component) {
  1580.     setpwent()
  1581.     while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
  1582.     nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
  1583.     for (i = 1; i <= nelem; i++) {
  1584.         Component = Elem[i]
  1585.         if (Component == "" && nelem > 1)
  1586.         continue
  1587.         if (Component in _RealInd)
  1588.         _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
  1589.         else
  1590.         _RealInd[Component] = PWEnt[PW_RECORD]
  1591.     }
  1592.     }
  1593.     _realIndDone = 1
  1594. }
  1595.  
  1596. # Make Name into a pattern that will match a name that contains all of the
  1597. # same name components (sequences of alphanums) in the same order.  If Name
  1598. # contains no name components, a null string is returned.
  1599. function MakeNamePat(Name,  Elem,nelem,i,Pat,e) {
  1600.     nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
  1601.     for (i = 1; i <= nelem; i++) {
  1602.     if ((e = Elem[i]) == "")
  1603.         continue
  1604.     if (Pat == "")
  1605.         Pat = "(^|[^a-zA-Z0-9])" e
  1606.     else
  1607.         Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
  1608.     }
  1609.     if (Pat == "")    # If Name contained no alphanums...
  1610.     return ""
  1611.     Pat = Pat "([^a-zA-Z0-9]|$)"
  1612.     return Pat
  1613. }
  1614.  
  1615. # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
  1616. # PWGetFields()) field matches Real.  Matching occurs if the alphanumeric
  1617. # components of Real occur in the same order in the entry.  Non-alphanums are
  1618. # ignored.  All of the components in Real must occur in the entry, but not all
  1619. # of the components in the entry must occur in Real.
  1620. # If the given name does not exist in the password file,
  1621. # the return value is ":" if Field was passed, 0 if not.
  1622. # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
  1623. # PW_REAL field matches the last previous Real parameter passed.
  1624. # In this case,  if the last entry has already been returned,
  1625. # the return value is ":" if Field was passed, 0 if not.
  1626. # Different IgnoreCase and Full parameters may be given when doing a Next
  1627. # search.  Both must always be passed; they do not default to the original
  1628. # values when doing a Next search.  The only parameter ignored when doing a
  1629. # Next search is Real.
  1630. # If IgnoreCase is true, case is ignored when searching.
  1631. # If Full is true, a match of the full name is required (including any
  1632. # punctuation).
  1633. # For successful return values and Field parameter explanation,
  1634. # see PWGetFields()
  1635. # Globals: For the Next search, between invokations these varies store values:
  1636. # _getpwrealInd[]: The set of pw indices that matched the query.
  1637. # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
  1638. # _getpwrealReal: The Real value passed with the original query.
  1639. # _getpwrealPat: Real converted to a component order search pattern.
  1640. function getpwreal(Real,PWEnt,Field,IgnoreCase,Full,Next,  ind,name,Pat) {
  1641.     if (!Next) {
  1642.     if (!PW_NAME)
  1643.         ReadPasswd()
  1644.     if (!_realIndDone)
  1645.         _makeRealInd()
  1646.     _getpwrealReal = Real
  1647.     _getpwrealPat = MakeNamePat(Real)
  1648.     # Get first component from Real
  1649.     Real = tolower(Real)
  1650.     gsub("^[^a-z0-9]+","",Real)
  1651.     gsub("[^a-z0-9].*","",Real)
  1652.     if (!(Real in _RealInd))
  1653.         return Field ? ":" : 0
  1654.     split(_RealInd[Real],_getpwrealInd,",")
  1655.     _getpwrealIndInd = 1
  1656.     }
  1657.     if (Full)
  1658.     Pat = _getpwrealReal
  1659.     else
  1660.     Pat = _getpwrealPat
  1661.     if (IgnoreCase)
  1662.     Pat = tolower(Pat)
  1663.     while (_getpwrealIndInd in _getpwrealInd) {
  1664.     ind = _getpwrealInd[_getpwrealIndInd++]
  1665.     name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
  1666.     if (IgnoreCase)
  1667.         name = tolower(name)
  1668.     if (Full ? (name == Pat) : (name ~ Pat))
  1669.         return PWGetFields("",PWEnt,Field,ind)
  1670.     }
  1671.     return Field ? ":" : 0
  1672. }
  1673.  
  1674. ### End pwent library
  1675. ### Begin qsort routines
  1676.  
  1677. # Arr[] is an array of values with arbitrary indices.
  1678. # k[] is returned with numeric indices 1..n.
  1679. # The values in k[] are the indices of Arr[],
  1680. # ordered so that if Arr[] is stepped through
  1681. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1682. # through in order of the values of its elements.
  1683. # The return value is the number of elements in the arrays (n).
  1684. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1685.     ElNum = 0
  1686.     for (ArrInd in Arr)
  1687.     k[++ElNum] = ArrInd
  1688.     qsortSegment(Arr,k,1,ElNum)
  1689.     return ElNum
  1690. }
  1691.  
  1692. # Sort a segment of an array.
  1693. # Arr[] contains data with arbitrary indices.
  1694. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1695. # This function sorts the elements of arr that are pointed to by
  1696. # k[start..end], swapping the values of elements of k[] so that
  1697. # when this function returns arr[k[start..end]] will be in order.
  1698. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1699.     # handle two-element case explicitly for a tiny speedup
  1700.     if ((end - start) == 1) {
  1701.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1702.         k[start] = tmpe
  1703.         k[end] = tmps
  1704.     }
  1705.     return
  1706.     }
  1707.     # Make sure comparisons act on these as numbers
  1708.     left = start+0
  1709.     right = end+0
  1710.     sepval = Arr[k[int((left + right) / 2)]]
  1711.     # Make every element <= sepval be to the left of every element > sepval
  1712.     while (left < right) {
  1713.     while (Arr[k[left]] < sepval)
  1714.         left++
  1715.     while (Arr[k[right]] > sepval)
  1716.         right--
  1717.     if (left < right) {
  1718.         tmp = k[left]
  1719.         k[left++] = k[right]
  1720.         k[right--] = tmp
  1721.     }
  1722.     }
  1723.     if (left == right)
  1724.     if (Arr[k[left]] < sepval)
  1725.         left++
  1726.     else
  1727.         right--
  1728.     if (start < right)
  1729.     qsortSegment(Arr,k,start,right)
  1730.     if (left < end)
  1731.     qsortSegment(Arr,k,left,end)
  1732. }
  1733.  
  1734. # Arr[] is an array of values with arbitrary indices.
  1735. # k[] is returned with numeric indices 1..n.
  1736. # The values in k are the indices of Arr[],
  1737. # ordered so that if Arr[] is stepped through
  1738. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1739. # through in order of the values of its indices.
  1740. # The return value is the number of elements in the arrays (n).
  1741. # If the indexes are numeric, Numeric should be true, so that they can be
  1742. # compared as such rather than as strings.  Numeric indexes do not have to be
  1743. # contiguous.
  1744. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1745.     ElNum = 0
  1746.     if (Numeric)
  1747.     # Indexes do not preserve numeric type, so must be forced
  1748.     for (ArrInd in Arr)
  1749.         k[++ElNum] = ArrInd+0
  1750.     else
  1751.     for (ArrInd in Arr)
  1752.         k[++ElNum] = ArrInd
  1753.     qsortNumIndByValue(k,1,ElNum)
  1754.     return ElNum
  1755. }
  1756.  
  1757. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1758. # by value.
  1759. # start and end are the starting and ending indexes of the range to be sorted.
  1760. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1761.     # handle two-element case explicitly for a tiny speedup
  1762.     if ((start - end) == 1) {
  1763.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1764.         Arr[start] = tmpe
  1765.         Arr[end] = tmps
  1766.     }
  1767.     return
  1768.     }
  1769.     left = start+0
  1770.     right = end+0
  1771.     sepval = Arr[int((left + right) / 2)]
  1772.     while (left < right) {
  1773.     while (Arr[left] < sepval)
  1774.         left++
  1775.     while (Arr[right] > sepval)
  1776.         right--
  1777.     if (left <= right) {
  1778.         tmp = Arr[left]
  1779.         Arr[left++] = Arr[right]
  1780.         Arr[right--] = tmp
  1781.     }
  1782.     }
  1783.     if (start < right)
  1784.     qsortNumIndByValue(Arr,start,right)
  1785.     if (left < end)
  1786.     qsortNumIndByValue(Arr,left,end)
  1787. }
  1788.  
  1789. ### End qsort routines
  1790.